package docbook2html5;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URLDecoder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.FileUtils;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.Result;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.apache.xml.resolver.tools.CatalogResolver;
import org.apache.xml.resolver.Catalog;
import org.apache.xml.resolver.CatalogManager;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;


/**
 * Class Transformation transforms files written in Docbook / Docbook Slides into HTML5
 */
public class Transformation {
    private static File docbookStylesheet;
    private static File xhtml2html5Stylesheet;
    private static final String path = "docbook2html-xsl";
    private String jarPath;
    private String fileName;
    private Transformer DBtm;
    private Transformer X2Htm;
    private File outputDir;
    private Document xmlDom;
    private DOMResult xhtml;

    /**
     * Creates a new instance of class Transformation
     * @param file a source file to transform
     * @param background a background file (optional else is null)
     * @param outputDir a directory to save the transformated document
     * @throws TransformationException
     */
    public Transformation(File file, File outputDir, File background) throws TransformationException{
        try{
            this.jarPath = URLDecoder.decode(((new File(Transformation.class.getProtectionDomain().getCodeSource().getLocation().getPath())).getParentFile().getPath() + "/"), "UTF-8");
        }catch(java.io.UnsupportedEncodingException ex){
            throw new TransformationException("Can not decode path of JAR" + ex);
        }
        this.fileName = file.getName().substring(0, file.getName().lastIndexOf('.'));
        this.outputDir = outputDir;
        this.xhtml = new DOMResult();
        this.xmlDom = ParseFiles(file);
        if(this.isSlides(xmlDom)){
            docbookStylesheet = new File(jarPath + path + "/docbook-xsl/slides/xhtml/flat.xsl");
        } else {
            docbookStylesheet = new File(jarPath + path + "/docbook-xsl/xhtml/docbook.xsl");
        }
        xhtml2html5Stylesheet = new File(jarPath + path + "/xhtml2html5.xsl");

        try {
            FileUtils.copyDirectory(new File(jarPath + path + "/output/css"), new File(outputDir.getPath() + "/css"));
            FileUtils.copyDirectory(new File(jarPath + path + "/output/images"), new File(outputDir.getPath() + "/images"));
            FileUtils.copyDirectory(new File(jarPath + path + "/output/js"), new File(outputDir.getPath() + "/js"));
        } catch (IOException ex) {
            throw new TransformationException("Error when copying the additional files");
        }
        
        if(background != null){
            try{
                FileUtils.copyFile(background, new File(outputDir.getPath() + "/images/background.jpg"));
            }catch (IOException ex) {
                throw new TransformationException("Error when copying the background file");
            }
        }

        if(!docbookStylesheet.exists()){
            throw new TransformationException("Docbook xsl stylesheet was not found\n Tried to find it in "+docbookStylesheet.getAbsolutePath());
        }

        if(!xhtml2html5Stylesheet.exists()){
            throw new TransformationException("xhtml2html5 xsl stylesheet was not found\n Tried to find it in "+xhtml2html5Stylesheet.getAbsolutePath());
        }

        try {            
            TransformerFactory transFact = TransformerFactory.newInstance();
            Templates docbookTemplate = transFact.newTemplates(new StreamSource(docbookStylesheet));
            DBtm = docbookTemplate.newTransformer();           
            Templates xhtml2html5Template = transFact.newTemplates(new StreamSource(xhtml2html5Stylesheet));
            X2Htm = xhtml2html5Template.newTransformer();
        } catch (TransformerConfigurationException ex) {
            throw new TransformationException("Unable to process xsl stylesheet: " + ex);
        }
    }
    
    /**
     * Testing if the file is Docbook or Docbook Slides.
     * @param file testing file in DOM structure
     * @return false or true
     * @throws TransformationException
     */
    public boolean isSlides(Document file) throws TransformationException{
        try {
          Node n = file.getFirstChild();
          if(n.getNodeName().equals("slides")){
              return true;
          }else{
              return false;
          }
        } catch (java.lang.Exception ex) { 
            throw new TransformationException("Cannot load document: "+ex.toString());
        }     
    }

    /**
     * Transforms the document into DOM result - XHTML
     * @throws TransformationException
     */
    public void TransformDB() throws TransformationException{
        try {                     
            DBtm.transform(new DOMSource(xmlDom), xhtml);            
        } catch (TransformerException ex) {
            throw new TransformationException("Error while transformating the document: " + ex);
        }
    }

    /**
     * Transforms the DOM result from function TransformDB() into the html5 file 
     * and testing if file contains attribute longdesc, if yes call function TransformLongdesc
     * @throws TransformationException
     */
    public void TransformX2Htm() throws TransformationException{ 
        File destFile = new File(outputDir.getPath() + '/' + fileName + ".html");
        Result res = new StreamResult(destFile);
        try {       
            Document doc = (Document)xhtml.getNode();
            NodeList img = doc.getElementsByTagName("img");
            String longdesc = null;
            for(int i=0; i < img.getLength(); i++){
                Element e =(Element) img.item(i);
                longdesc = e.getAttribute("longdesc");
                if(longdesc.compareTo("") != 0 ){
                    this.TransformLongdesc(longdesc);
                }
            }                     
            X2Htm.transform(new DOMSource(doc), res);
        } catch (TransformerException ex) {
            throw new TransformationException("Error while transformating the document: " + ex);
        } 
    } 
    
    /**
     * Transforms the xhtml file into html5 file, 
     * this function is call only if DOM result - XHTML contains attribute longdesc
     * @param file path to the file
     * @throws TransformationException
     */
    public void TransformLongdesc(String file) throws TransformationException{
        File destFile = new File(outputDir.getPath() + '/' + file);
        Result result = new StreamResult(destFile);
        File help = new File(file);
        try {
            BufferedReader input = new BufferedReader(new FileReader(help));
            String line = null;
            while ((line = input.readLine()) != null) {
                Matcher junkMatcher = (Pattern.compile("^([\\W]+)<")).matcher(line.trim());
                line = junkMatcher.replaceFirst("<");
            }
            input.close();
            Document doc = ParseFiles(help);
            DOMSource tmpsrc = new DOMSource(doc);
            X2Htm.transform(tmpsrc, result);
            help.delete();
        } catch (IOException ex) {
            throw new TransformationException("Error can't find the document: " + ex); 
        } catch (TransformerException ex) {
            throw new TransformationException("Error while transformating the document: " + ex);
        }
    }
    
    /**
     * Parse input file to DOM structure
     * @param file file to parse
     * @return Document in DOM structure
     * @throws TransformationException
     */
    public Document ParseFiles(File file) throws TransformationException{
        try{
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setValidating(true);
            DocumentBuilder parser = factory.newDocumentBuilder();
            parser.setErrorHandler(new ErrorReporter());
            CatalogManager ignored = new CatalogManager();
            ignored.setIgnoreMissingProperties(true);
            CatalogResolver resolver = new CatalogResolver(ignored);
            Catalog catalog = resolver.getCatalog();
            catalog.parseCatalog("file:///" + jarPath + "catalog.xml");
            parser.setEntityResolver(resolver);
            Document doc = parser.parse(file);
            return doc;
        } catch (javax.xml.parsers.ParserConfigurationException ex) {
            throw new TransformationException("Error while making parser: " + ex);
        } catch (org.xml.sax.SAXException ex) {
            throw new TransformationException("Error while parsering the document: " + ex);
        } catch (java.io.IOException ex) {
            throw new TransformationException("Error while parsering the document: " + ex);
        }
    }
    
    /**
     * Error Reporter throws error or warnings which was found during parsing
     * @throws SAXException
     */
    class ErrorReporter implements ErrorHandler{
      public void warning(SAXParseException exception){
          System.err.println("Warning: " + exception.getMessage());
      }
      public void error(SAXParseException exception){
          System.err.println("Warning: " + exception.getMessage());
      }  
      public void fatalError(SAXParseException exception) throws SAXException{
          throw new SAXException(exception.getMessage());
      }          
   }
}
